/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.bef.core.action.save;

import com.inspur.edp.bef.api.befruntimeconfig.RuntimeConService;
import com.inspur.edp.bef.api.exceptions.DataVersionMismatchException;
import com.inspur.edp.bef.core.DebugAdapter;
import com.inspur.edp.bef.core.DotNetToJavaStringHelper;
import com.inspur.edp.bef.core.be.BEManager;
import com.inspur.edp.bef.core.be.BENodeEntity;
import com.inspur.edp.bef.core.be.BusinessEntity;
import com.inspur.edp.bef.core.be.CoreBEContext;
import com.inspur.edp.bef.core.lock.LockAdapter;
import com.inspur.edp.bef.core.lock.LockUtils;
import com.inspur.edp.bef.core.scope.ScopeNodeParameter;
import com.inspur.edp.bef.core.session.FuncSession;
import com.inspur.edp.bef.core.session.FuncSessionManager;
import com.inspur.edp.bef.spi.entity.CodeRuleInfo;
import com.inspur.edp.cdp.coderule.api.CodeRuleBEAssignService;
import com.inspur.edp.cdp.common.utils.spring.SpringUtil;
import com.inspur.edp.cef.api.RefObject;
import com.inspur.edp.cef.api.repository.IRootRepository;
import com.inspur.edp.cef.entity.changeset.ChangeType;
import com.inspur.edp.cef.entity.changeset.IChangeDetail;
import com.inspur.edp.cef.entity.changeset.ModifyChangeDetail;
import com.inspur.edp.cef.entity.changeset.Tuple;
import com.inspur.edp.cef.entity.entity.IEntityData;
import com.inspur.edp.cef.entity.entity.IEntityDataCollection;
import com.inspur.edp.cef.entity.repository.DataSaveParameter;
import com.inspur.edp.cef.entity.repository.DataSaveResult;
import com.inspur.edp.cef.spi.entity.IAuthFieldValue;
import com.inspur.edp.cef.spi.scope.AbstractCefScopeExtension;
import com.inspur.edp.udt.entity.ISimpleUdtData;
import io.iec.edp.caf.commons.utils.SpringBeanUtils;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.var;
import org.springframework.util.StringUtils;

public class SaveWithScopeExtension extends AbstractCefScopeExtension {

  private final List<SaveScopeNodeParameter> getParams() {
    return super.getParamsGeneric();
  }

  @Override
  public void onSetComplete() {
    FuncSession currentSession = FuncSessionManager.getCurrentSession();
    trace(DebugAdapter.Mark_Begin, currentSession.getSessionId(), null);
    HashMap<String, ArrayList<SaveScopeNodeParameter>> groups = new HashMap<>();
    for (SaveScopeNodeParameter par : getParams()) {
      if(!((CoreBEContext) par.getBEContext()).hasChange()) {
        continue;
      }
      String type = par.getBEContext().getBizEntity().getBEType();
      ArrayList<SaveScopeNodeParameter> list = groups.computeIfAbsent(type, (k) -> new ArrayList());
      list.add(par);
    }
    for (Map.Entry<String, ArrayList<SaveScopeNodeParameter>> group : groups.entrySet()) {
      BEManager manager = (BEManager) currentSession.getFuncSessionItem(group.getKey()).getBEManager();
      trace("funcRun_savingBE", group.getKey(), null);
//      LcpUtil.error("[bef]准备保存到持久:" + currentSession.getSessionId() + ";"+group.getKey());
      String mobileLockGroup = null;
      if (LockAdapter.getInstance().isMobileClient()) {
        mobileLockGroup = lock4MobileClient(group);
      }
      try {
        checkVersion(manager, group);
        List<Tuple<IChangeDetail, DataSaveParameter>> chgs = new ArrayList<>(
            group.getValue().size());
        ArrayList<CodeRuleParam> release = new ArrayList<>();
        for (SaveScopeNodeParameter par : group.getValue()) {
          IChangeDetail change = par.getBEContext().getTransactionalChange();
          if (change.getChangeType() == ChangeType.Deleted) {
            collectReleaseCode(par, release);
          }
          if (change.getChangeType() == ChangeType.Modify) {
            collectChildrenReleaseCode(change, par, release, par.getBEContext().getOriginalData(),
                (BENodeEntity) par.getBEContext().getBizEntity());
          }
          chgs.add(new Tuple<>(change, manager.getDataSavePar(change.getDataID())));
        }
        if (!chgs.isEmpty()) {
          IRootRepository repository = manager.getRepository();
          List<DataSaveResult> saveResults;
          try {
            trace("funcRunning_persisting", chgs, null);
//            LcpUtil.error("[bef]保存变更集到数据库:", chgs);
            saveResults = repository.save(chgs);
          } catch (SQLException throwables) {
            throw new RuntimeException(throwables);
          }
          trace("funcRunning_afterSaveVal", null, null);
          //LcpUtil.error("[bef]保存执行后校验");
          for (int i = 0; i < chgs.size(); i++) {
            manager.onAfterSaveValidate(chgs.get(i).getItem1().getDataID(), saveResults.get(i));
          }
          trace("funcRunning_afterSaveValSuccess", null, null);
//          LcpUtil.error("[bef]保存执行后校验完成");
        }
        releaseCode(manager.getBEInfo().getBEID(), release);
        trace("funcRun_savedBE", group.getKey(), null);
      } finally {
        if(mobileLockGroup != null)
          LockAdapter.getInstance().releaseLock(mobileLockGroup);
      }
    }
    //TODO: 与张伟庆讨论, 释放编号规则不受外部事务回滚影响, 所以此处如果后续处理产生异常再次执行保存可能导致重复释放,
    // 编号规则应该支持相同编号支持重复释放, 目前据说重复释放会导致重复生成, 于21/04/16讨论后其正在安排计划修改
  }

  private void releaseCode(String beId, ArrayList<CodeRuleParam> list) {
    if (list.isEmpty()) {
      return;
    }
    CodeRuleBEAssignService service = SpringBeanUtils.getBean(CodeRuleBEAssignService.class);
    for (CodeRuleParam item : list) {
      HashMap<String,Object> map=new HashMap<>();
      map.put(item.getNodeCode(), item.getData());
      service.release(beId, item.getNodeCode(), item.getLabelId(), map, item.getRuleId(),
          item.getCode());
    }
  }

  private void collectChildrenReleaseCode(IChangeDetail change, SaveScopeNodeParameter par,
      ArrayList<CodeRuleParam> release, IEntityData originalData, BENodeEntity parentEntity) {
    ModifyChangeDetail modifyChangeDetail = (ModifyChangeDetail) change;
    if (modifyChangeDetail.getChildChanges() == null
        || modifyChangeDetail.getChildChanges().size() == 0) {
      return;
    }
    for (Map.Entry<String, Map<String, IChangeDetail>> childChanges : modifyChangeDetail
        .getChildChanges().entrySet()) {
      if (childChanges.getValue() == null || childChanges.getValue().size() == 0) {
        continue;
      }
      String nodeCode = childChanges.getKey();
      IEntityDataCollection childDatas = originalData.getChilds().get(nodeCode);
      for (Map.Entry<String, IChangeDetail> childChange : childChanges.getValue().entrySet()) {
        switch (childChange.getValue().getChangeType()) {
          case Added:
            continue;
          case Modify: {
            IEntityData data = childDatas.getItem(childChange.getKey());
            BENodeEntity childEntity = (BENodeEntity) parentEntity
                .getChildBEEntity(nodeCode, data.getID());
            collectChildrenReleaseCode(childChange.getValue(), par, release, data, childEntity);
            break;
          }
          case Deleted: {
            IEntityData data = childDatas.getItem(childChange.getKey());
            BENodeEntity childEntity = (BENodeEntity) parentEntity
                .getChildBEEntity(nodeCode, data.getID());
            var beforeSave = childEntity.getBeforeSaveCodeRule();
            addCodeRuleParam(beforeSave, release, data, childEntity.getContext().getCode());
            var afterCreate = childEntity.getAfterCreateCodeRule();
            addCodeRuleParam(afterCreate, release, data, childEntity.getContext().getCode());
            break;
          }
        }
//        if(childChange.getValue().getChangeType()!=ChangeType.Deleted)
//          continue;
//        IEntityData data=childDatas.getItem(childChange.getKey());
//        BENodeEntity childEntity = (BENodeEntity) parentEntity.getChildBEEntity (nodeCode,data.getID());
//        //BENodeEntity) ((BusinessEntity) par.getBEContext().getBizEntity()).getChildBEEntity(nodeCode,data.getID());
//        if(childChange.getValue().getChangeType()==ChangeType.Modify)
//        {
//          collectChildrenReleaseCode(childChange.getValue(),par,release,data,childEntity);
//        }
//
//        var beforeSave = childEntity.getBeforeSaveCodeRule();
//        addCodeRuleParam(beforeSave, release, data);
//        var afterCreate = childEntity.getAfterCreateCodeRule();
//        addCodeRuleParam(afterCreate, release, data);

      }
    }
  }

  private void collectReleaseCode(ScopeNodeParameter par,
      java.util.ArrayList<CodeRuleParam> release) {
    var entity = (BusinessEntity) par.getBEContext().getBizEntity();
    var data = par.getBEContext().getOriginalData();

    var beforeSave = entity.getBeforeSaveCodeRule();
    addCodeRuleParam(beforeSave, release, data, entity.getNodeCode());

    var afterCreate = entity.getAfterCreateCodeRule();
    addCodeRuleParam(afterCreate, release, data, entity.getNodeCode());
  }

  private void addCodeRuleParam(HashMap<String, CodeRuleInfo> codeRules,
      ArrayList<CodeRuleParam> result, IEntityData data, String nodeCode) {
    if (codeRules == null) {
      return;
    }
    for (String key : codeRules.keySet()) {
      String code = getCodeValue(data, key);
      if (DotNetToJavaStringHelper.isNullOrEmpty(code)) {
        continue;
      }

      CodeRuleParam param = new CodeRuleParam();
      param.setCode(code);
      param.setLabelId(key);
      param.setRuleId(codeRules.get(key).getCodeRuleId());
      param.setData(data);
      param.setNodeCode(nodeCode);
      result.add(param);
    }
  }

  private String getCodeValue(IEntityData data, String codePropertyName) {
    Object tempVar = data.getValue(codePropertyName);
    String result = "";
    IAuthFieldValue authFieldValue = (IAuthFieldValue) ((tempVar instanceof IAuthFieldValue)
        ? tempVar : null);
    if (tempVar != null && tempVar instanceof ISimpleUdtData) {
      ISimpleUdtData data1 = (ISimpleUdtData) tempVar;
      if (data1.getValue() != null && data1.getValue() instanceof IAuthFieldValue) {
        result = ((IAuthFieldValue) data1.getValue()).getValue();
      } else if (data1.getValue() == null) {
        result = null;
      } else {
        result = String.valueOf(data1.getValue());
      }
    } else if (authFieldValue == null) {
      result = String.valueOf(data.getValue(codePropertyName));
    } else {
      result = authFieldValue.getValue();
    }
    return result;
  }

  private static void checkVersion(BEManager manager,
      Entry<String, ArrayList<SaveScopeNodeParameter>> group) {
    RuntimeConService service = SpringUtil.getBean(RuntimeConService.class);
    if(!LockAdapter.getInstance().isMobileClient() && !service.isUseIgnoreLock())//资金自定义动作中切换session同时使用lcp和实体动作修改一条数据的场景会导致报错, 暂时增加兼容, 版本检查也很重要考虑一段时间后去掉兼容
      return;
    IRootRepository repository = manager.getRepository();
    if (!StringUtils.isEmpty(
        group.getValue().get(0).getBEContext().getBizEntity().getVersionControlPropertyName())) {
      for (SaveScopeNodeParameter par : group.getValue()) {
        if (par.getBEContext().isNew() || par.getBEContext().getOriginalData() == null) {
          continue;
        }
        Date currVer = (Date)par.getBEContext().getOriginalData().getValue(par.getBEContext().getBizEntity().getVersionControlPropertyName());
        Date repoVer = repository.getVersionControlValue(par.getBEContext().getID());
        if (repoVer != null && repoVer.compareTo(new Date(0)) != 0
            && (currVer == null || repoVer.compareTo(currVer) != 0)) {
          throw new DataVersionMismatchException(group.getKey(),
              Arrays.asList(par.getBEContext().getID()));
        }
      }
    }
  }

  private static String lock4MobileClient(Entry<String, ArrayList<SaveScopeNodeParameter>> group) {
    List<String> ids = new ArrayList(group.getValue().size());
    for (SaveScopeNodeParameter par : group.getValue()) {
      if (par.getBEContext().isNew()) {
        continue;
      }
      ids.add(par.getBEContext().getID());
    }
    if (ids.isEmpty()) {
      return null;
    }
    RefObject<String> groupId = new RefObject<>(null);
    if (!LockAdapter.getInstance().addLock(group.getKey(), ids, groupId, new RefObject<>(null))) {
      LockUtils.throwLockFailed(ids, null);
    }
    return groupId.argvalue;
  }

  private final static void trace(String mark, Object info, Exception e){
    DebugAdapter.trace("SaveExt", mark, e, info);
  }
}
